/**
 * Copyright (c) 2016  
 */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>

#include "ini.h"


/* Case insensitive string compare */
static int strcmpci(const char *a, const char *b) {
  for (;;) {
    int d = tolower(*a) - tolower(*b);
    if (d != 0 || !*a) {
      return d;
    }
    a++, b++;
  }
}

/* Returns the next string in the split data */
static char* next(Ini_TypeDef *ini, char *p) {
  p += strlen(p);
  while (p < ini->end && *p == '\0') {
    p++;
  }
  return p;
}

static void trim_back(Ini_TypeDef *ini, char *p) {
  while (p >= ini->data && (*p == ' ' || *p == '\t' || *p == '\r')) {
    *p-- = '\0';
  }
}

static char* discard_line(Ini_TypeDef *ini, char *p) {
  while (p < ini->end && *p != '\n') {
    *p++ = '\0';
  }
  return p;
}


static char *unescape_quoted_value(Ini_TypeDef *ini, char *p) {
  /* Use `q` as write-head and `p` as read-head, `p` is always ahead of `q`
   * as escape sequences are always larger than their resultant data */
  char *q = p;
  p++;
  while (p < ini->end && *p != '"' && *p != '\r' && *p != '\n') {
    if (*p == '\\') {
      /* Handle escaped char */
      p++;
      switch (*p) {
        default   : *q = *p;    break;
        case 'r'  : *q = '\r';  break;
        case 'n'  : *q = '\n';  break;
        case 't'  : *q = '\t';  break;
        case '\r' :
        case '\n' :
        case '\0' : goto end;
      }

    } else {
      /* Handle normal char */
      *q = *p;
    }
    q++, p++;
  }
end:
  return q;
}

static int convert_s_to_int(int *integer,char *str,int length)
{
    int i;
    *integer=0;
    for(i=0;i<length;i++){
        if(str[i]<'0' || str[i]>'9'){
            break;
        }
        *integer*=10;
        *integer+=str[i]-'0';
    }
    return i;
}

/* end */
void ini_get_struct(Ini_TypeDef *ini, const char *section, const char *key ) {
  printf(" test ->   %d \n ", sizeof(ini) );
  printf(" test ->   %d \n ", sizeof(Ini_TypeDef) );
 	const char *name = ini_get(ini, section, key);

  printf(" ini_get_struct ->     %s \n ", name );

  int flag = 0;
  char   tmp[16] ={0};
  int size = 0;
  char * ptr = (char *) name;
  while( *ptr != '\0' ) {
    

    if(*ptr == ','){

      flag++;
      // 转换为数字保存  ptr 重置
      switch (flag)
      {
        case 1:
          convert_s_to_int(&ini->para.seq, tmp, size);
          break;
        case 2:
          memcpy((void*)ini->para.name, tmp, size);
          break;
        case 3:
          memcpy((void*)ini->para.type, tmp, size);
          break;    
        case 4:
          memcpy((void*)ini->para.unit, tmp, size);
          break;  
        case 5:
          convert_s_to_int(&ini->para.val, tmp, size);
          break;    
        case 6:
          convert_s_to_int(&ini->para.coeff_retention, tmp, size);
          break;  
        default:
          break;
      }
      // printf(" case ----     %s \n " , tmp);
      memset( tmp, 0, sizeof(tmp));
      size = 0;
      ptr++;
      continue;
      // ptr = (char *) name;
    }
    else {
      memcpy((void*)tmp+size,(void*)ptr, 1);
      size++;
      ptr++;
      continue;
    }



  }

}

void ini_print_struct(Ini_TypeDef *ini ) {
  printf(" res ->   %d \n ",  ini->para.seq);
  printf(" res ->   %s \n ",  ini->para.name);
  printf(" res ->   %s \n ",  ini->para.type);
  printf(" res ->   %s \n ",  ini->para.unit);
  printf(" res ->   %d \n ",  ini->para.val);
  printf(" res ->   %d \n ",  ini->para.coeff_retention);
}

/*   [setting^^^pH^^^1,pH,float,PH,78,10^^DO^^^2^^[para^^^pH^^^1^^salt^^^99999a  */
void ini_print_data(Ini_TypeDef *ini ) {
  char *p = ini->data;
  while (p < ini->end) {
    if (*p == '\0') {
    printf("^");
    } 
    else {
    printf("%c",*p);
    }
    p++;
  }
  printf("\r\n  end: %d \r\n",ini->end);
}

// TODO 判断是否有section 或key 
void ini_get_start_end(Ini_TypeDef *ini, const char *section, const char *key, int *start,  int *end) {

  int flag = 0;  /* 1:  [,  2: section ok  3: key ok */
  char section_[16] = { 0 };
  char section_size = 0;
  int section_exsited = 0;

  *end =0;

  char *p = ini->data;
  while (p < ini->end) {
    switch (*p)
    {
        case '[':
          if (section_exsited == 0) {
            flag =1;
            memset((void*)section_, 0, sizeof(section_));
            section_size = 0;
            break;
          }

          if (section_exsited == 1) {
            *start = p;
            *end = p;
          }

        case '\0':
          /* section end */
          if (flag ==1 && (*(p+1) == '\0') && (*(p+2) == '\0') )
          {
            if (strcmp(section_, section) == 0)
            {
              // printf(" ==== -> flag == 2  %s   %s  \n ", section_,section);
              section_exsited = 1;
              flag = 2;
              *start =  p+2 ;  /* 开始点*/
              // printf(" ==== -> flag == 2  %d   %d \n ", *start, p);
            }
            else {
              flag = 0;
            }
            break;
          }

          /* key end */
          if( flag == 2  && (*(p+1) == '\0') && (*(p+2) == '\0') ){
            // printf(" ==== -> flag == 3  %d\n ", p );
            flag = 3;
            break;
          }

          /* value end */
          if( flag == 3  && (*(p+1) == '\0') && (*(p+1) == '\0')){
            // *end = (int)(p);
            flag = 4;
            // printf(" ==== -> key_start %d key_end  %d \n ",*start, *end );
            // return;
          break;    
          }

          if( flag == 4  && (*(p+1) == '\0')  ){
            *end =  p ;
            flag = 5;
            // printf(" ==== -> key_start %d key_end  %d \n ",*start, *end );
            break;
          }
          break;    

      default:
          if (flag == 1) 
          {
            // printf(" flag 1   %c \n ",*p    );
            memcpy( (void*)(section_+section_size), p, 1 );
            section_size++;
          }
          // p++;
          break;
    }

    if (flag == 5) return;
    p++;
  }
}

/* 写到文件再读出, end=0  插入在section最后一项 */
void ini_insert_struct(Ini_TypeDef *ini, const char *section, const char *key ,int start, int end) {

  // char *key_start, *key_end;
  int  key_start, key_end;
  char *buf;
  char node[128] = {0};

  // ini_get_start_end( ini,  section,  key,  &key_start,   &key_end );

  // /* 结构体组装数据插入  , 保存再读取 */
  snprintf( node, 128, "%d,%s,%s,%s,%d,%d"
      , ini->para.seq, ini->para.name, ini->para.type, ini->para.unit, ini->para.val, ini->para.coeff_retention );
  // printf(" test ->  %s   %d \n ",  node ,sizeof(node) );  
  // printf(" ini->end =========->    %d \n " , ini->end-ini->data);  

  char *handle = malloc(ini->end-ini->data + 128);
  buf = handle;

  memset( buf, 0, sizeof(buf) );

  // printf(" ===== key_start ->  %d   %d  %d \n ", start ,  end ,ini->data);

  char *p = ini->data;
  while( p <= start ){
    *buf = *p;
    // printf(" ===== ->  %c  %c\n ",  *p ,*buf );
    buf++;
    p++;
  }

  // printf(" test ========  %s   \n ", buf );   

  p = node;
  while( *p!='\0' ){
    *buf = *p;
    // printf(" ===== ->  %c  %c\n ",  *p ,*buf );
    buf++;
    p++;
  }

  p = end;
  while( p < ini->end ){
    *buf = *p;
    // printf(" ===== ->  %c  %c\n ",  *p ,*buf );
    buf++;
    p++;
  }

  ini->data = handle;
  ini->end = buf;

}

/* fwrite 一次性写入, 或 fprintf 分别写入 不用分配内存*/
void ini_dump(Ini_TypeDef *ini, const char *filename ) {
  char *buf = malloc(ini->end-ini->data+64);
  int flag = 0;  /* 1:  [,  2: section ok  3: key ok */

  char *p = ini->data;
  while (p < ini->end) {
    switch (*p)
    {
    case '[':
      *buf = *p;
      flag = 1;
      break;

    case '\0':
      if (flag == 1 && *(p+1) == '\0' && *(p+2) == '\0') {
        *buf = ']';
        buf++;
        *buf = '\n';
        flag = 2;
        break;
      }

      if (flag == 2 && *(p+1) == '\0' && *(p+2) == '\0') {
        *buf = '\n';
        flag = 3;
        break;
      }

      if ( flag ==3 && *(p+1)== '\0'   && *(p+2) != '\0')  {
        *buf = '\n';
        flag = 2;
        break;
      }

      p--;
      break;    
    default:
      *buf = *p;
      break;
    }
    p++;
    buf++;

  }

  FILE *fp = NULL;
  fp = fopen(filename, "w");
  fwrite(ini->data, 1, ini->end-ini->data, fp);
  fclose(fp);
}

void ini_modify_struct_from_ui(Ini_TypeDef *ini, const char *seq, const char *name
  ,const char *type, const char *unit, const char *val, const char *coeff_retention) {

    convert_s_to_int(&ini->para.seq, (char*)seq, 16);
    memcpy((void*)ini->para.type, name, 16);
    memcpy((void*)ini->para.type, type, 16);
    memcpy((void*)ini->para.unit, unit, 16);
    convert_s_to_int(&ini->para.val, (char*)val, 16);
    convert_s_to_int(&ini->para.coeff_retention, (char*)coeff_retention, 16);
}


/* Splits data in place into strings containing section-headers, keys and
 * values using one or more '\0' as a delimiter. Unescapes quoted values */
static void split_data(Ini_TypeDef *ini) {
  char *value_start, *line_start;
  char *p = ini->data;

  while (p < ini->end) {
    switch (*p) {
      case '\r':
      case '\n':
      case '\t':
      case ' ':
        *p = '\0';
        /* Fall through */

      case '\0':
        p++;
        break;

      case '[':
        p += strcspn(p, "]\n");
        *p = '\0';
        break;

      case ';':
        p = discard_line(ini, p);
        break;

      default:
        line_start = p;
        p += strcspn(p, "=\n");

        /* Is line missing a '='? */
        if (*p != '=') {
          p = discard_line(ini, line_start);
          break;
        }
        trim_back(ini, p - 1);

        /* Replace '=' and whitespace after it with '\0' */
        do {
          *p++ = '\0';
        } while (*p == ' ' || *p == '\r' || *p == '\t');

        /* Is a value after '=' missing? */
        if (*p == '\n' || *p == '\0') {
          p = discard_line(ini, line_start);
          break;
        }

        if (*p == '"') {
          /* Handle quoted string value */
          value_start = p;
          p = unescape_quoted_value(ini, p);

          /* Was the string empty? */
          if (p == value_start) {
            p = discard_line(ini, line_start);
            break;
          }

          /* Discard the rest of the line after the string value */
          p = discard_line(ini, p);

        } else {
          /* Handle normal value */
          p += strcspn(p, "\n");
          trim_back(ini, p - 1);
        }
        break;
    }
  }
}



Ini_TypeDef* ini_load(const char *filename) {
  Ini_TypeDef *ini = NULL;
  FILE *fp = NULL;
  int n, sz;

  /* Init ini struct */
  ini = malloc(sizeof(*ini));
  if (!ini) {
    goto fail;
  }
  memset(ini, 0, sizeof(*ini));

  /* Open file */
  fp = fopen(filename, "rb");
  if (!fp) {
    goto fail;
  }

  /* Get file size */
  fseek(fp, 0, SEEK_END);
  sz = ftell(fp);
  rewind(fp);

  /* Load file content into memory, null terminate, init end var */
  ini->data = malloc(sz + 1);
  ini->data[sz] = '\0';
  ini->end = ini->data  + sz;
  n = fread(ini->data, 1, sz, fp);
  if (n != sz) {
    goto fail;
  }

  /* Prepare data */
  split_data(ini);

  /* Clean up and return */
  fclose(fp);

  // ini_data_2_struct(ini);
  return ini;

fail:
  if (fp) fclose(fp);
  if (ini) ini_free(ini);
  return NULL;
}


void ini_free(Ini_TypeDef *ini) {
    free(ini->data);
    free(ini);
}


const char* ini_get(Ini_TypeDef *ini, const char *section, const char *key) {
    char *current_section = "";
    char *val;
    char *p = ini->data;

    if (*p == '\0') {
        p = next(ini, p);
    }

    while (p < ini->end) {
        if (*p == '[') {
        /* Handle section */
        current_section = p + 1;

        } else {
        /* Handle key */
        val = next(ini, p);
        if (!section || !strcmpci(section, current_section)) {
            if (!strcmpci(p, key)) {
            return val;
            }
        }
        p = val;
        }

        p = next(ini, p);
    }

    return NULL;
}


// int ini_sget(
//   Ini_TypeDef *ini, const char *section, const char *key,
//   const char *scanfmt, void *dst
// ) {
//   const char *val = ini_get(ini, section, key);
//   if (!val) {
//     return 0;
//   }
//   if (scanfmt) {
//     sscanf(val, scanfmt, dst);
//   } else {
//     *((const char**) dst) = val;
//   }
//   return 1;
// }

// const char *name = ini_get(config, "owner", "name");
// if (name) {
//   printf("name: %s\n", name);
// }